# -*- coding: utf-8 -*-
"""
Created on 2021-6-6 16:06:24
客户端类定义
@author: 王韬
"""
import random
import sys
import socket
import time
import threading
import tkinter

import functions as fn


# ###################<<自定义线程类>>################### #
class MyThread(threading.Thread):
    """

    Attributes:
        func -- target function name without '()'
        *args -- arguments of func
    Methods:
    """
    def __init__(self, name, func, *args):
        super().__init__()
        self.name = name
        self.func = func
        self.args = args

    def run(self):
        self.func(*self.args)
        print('Thread:', self.name, 'start')


# ###################<<玩家类>>################### #
class Player:
    """玩家类

    Attributes:
        addr -- ip地址
        sock -- 客户端socket
        is_waiting -- 客户端在匹配中(default False)
        in_game -- 客户端在游戏中(default False)
    Methods:
    """
    def __init__(self):
        self.addr = None
        self.sock = None
        self.hall_args = {
            'p_num': 0,
            'is_connected': False,
            'is_waiting': False,
            'in_game': False
        }
        self.room_args = {
            'r_index': -1,
            'end_flag': False,
        }
        self.game_args = {
            'is_def': True,  # 守方
            'turn_num': 0,  # 回合数
            'my_cards': [],  # 守方手牌列表
            'his_cards': [],  # 攻方手牌列表
            'wait_flag': False,
            'obj': {
                'hp': 100
            },
            'me': {
                'wait_oprt': False,
                'turn_flag': False,
                'win_flag': False,
                'init_card_num': 0,
                'card_num': 0,
                'card_choose': 0,
            },
            'him': {
                'wait_oprt': False,
                'turn_flag': False,
                'win_flag': False,
                'init_card_num': 0,
                'card_num': 0,
                'card_choose': 0,
            }
        }

    def set_game_args(self, game_args):
        """set game arguments

        Arguments:
            game_args -- dictionary of game_args
        """
        for key in game_args.keys():
            self.game_args[key] = game_args[key]

    def send_game_args(self):

        send_data = self.game_args
        fn.send_msg(send_data, self.sock, info='game args')


# ###################<<大厅类>>################### #
class Hall:
    """大厅类

    Attributes:
        sock -- the host hall socket
        addr -- the host hall address
        clients -- the list of clients
        players -- the list of players
        p_num -- 已连接客户端数
        r_num -- 已创建的房间数

    Methods:
        start_hall
        client_cnt, client_del, recv_msg, server_window,
    """

    # 定义构造方法
    def __init__(self):
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # self.addr = ('192.168.124.24', 10000)
        self.addr = (fn.get_ip(), 10000)
        self.rlock = threading.RLock()
        self.clients = {}
        self.players_waiting = {}
        self.players = {}
        self.p_num = 0
        self.rooms = []
        self.r_num = 0

    def start_hall(self):
        """启动服务器大厅，保持监听

        Arguments:
        """
        try:
            self.sock.bind(self.addr)
        except Exception as e:
            print(e)

        self.sock.listen(10)
        t_list = []

        t_client_cnt = MyThread('t_client_cnt', self.client_cnt)
        t_list.append(t_client_cnt)

        t_server_window = MyThread('t_server_window', self.server_window)
        t_list.append(t_server_window)

        for t in t_list:
            t.start()

        for t in t_list:
            t.join()

        # self.client_cnt()

    def server_window(self):
        """

        Arguments:
            hall -- hall object
        Returns:
        """
        server_window = tkinter.Tk()
        server_window.title('server window')
        server_window.geometry('420x330')
        server_window.wm_attributes('-topmost', 1)

        # variable labels
        txt = []
        lbl = []
        for k in range(10):
            txt.append(tkinter.StringVar(server_window))
            txt[k].set('position '+str(k)+': waiting for client')
            lbl.append(tkinter.Label(server_window, textvariable=txt[k]))
            lbl[k].grid(column=0, row=k+1)

        for k in range(10, 20):
            txt.append(tkinter.StringVar(server_window))
            txt[k].set('position ' + str(k-10) + ': waiting for player')
            lbl.append(tkinter.Label(server_window, textvariable=txt[k]))
            lbl[k].grid(column=2, row=k-10+1)

        txt.append(tkinter.StringVar(server_window))
        txt[20].set('p_num:  '+str(self.p_num))
        lbl.append(tkinter.Label(server_window, textvariable=txt[20]))
        lbl[20].grid(column=0, row=12)

        txt.append(tkinter.StringVar(server_window))
        txt[21].set('r_num:  '+str(self.r_num))
        lbl.append(tkinter.Label(server_window, textvariable=txt[21]))
        lbl[21].grid(column=0, row=13)

        # constant labels
        lbl_connected = tkinter.Label(server_window, text='clients connected')
        lbl_waiting = tkinter.Label(server_window, text='players waiting')
        lbl_interval_0 = tkinter.Label(server_window, text='-------------------------------------')
        lbl_interval_1 = tkinter.Label(server_window, text='----')
        lbl_interval_2 = tkinter.Label(server_window, text='-------------------------------------')
        lbl_connected.grid(column=0, row=0)
        lbl_waiting.grid(column=2, row=0)
        lbl_interval_0.grid(column=0, row=11)
        lbl_interval_1.grid(column=1, row=11)
        lbl_interval_2.grid(column=2, row=11)

        btn_update = tkinter.Button(
                server_window, text='update data',
                command=lambda: self.tk_update(txt))
        btn_update.grid(column=2, row=12)
        # btn_quit = tkinter.Button(
        #         server_window, text='quit server',
        #         command=lambda: self.stop_server())
        # btn_quit.grid(column=2, row=13)

        server_window.mainloop()

    def tk_update(self, txt):
        """更新tkinter变量 txt

        Arguments:
            txt -- tkinter String variable to be update
        Returns:
        """
        for k in range(len(self.clients)):
            txt[k].set('client_'+str(k)+': '+str(list(self.clients.keys())[k]))
        for k in range(len(self.clients), 10):
            txt[k].set('position ' + str(k) + ': waiting for client')

        for k in range(10, 10+len(self.players_waiting)):
            txt[k].set('player_'+str(k-10)+': '+str(list(self.players_waiting.keys())[k-10]))
        for k in range(10+len(self.players_waiting), 20):
            txt[k].set('position ' + str(k-10) + ': waiting for client')

        txt[20].set('p_num:  ' + str(self.p_num))
        txt[21].set('r_num:  '+str(self.r_num))

    def client_cnt(self):
        """等待客户端连接, 保存已连接的客户端

        Arguments:
        Returns:
        """

        while True:
            conn, addr = self.sock.accept()

            self.rlock.acquire()

            self.clients[addr] = conn
            self.p_num = len(self.clients)
            print(self.p_num, 'clients connected:', self.clients.keys())
            send_data = dict(is_connected=True)
            fn.send_msg(send_data, conn)

            for client in self.clients.values():
                # # send_data = '{\'p_num\':' + str(self.p_num) + '}'
                # send_data = str(dict(p_num=self.p_num))
                # client.send(send_data.encode("utf-8"))

                send_data = dict(p_num=self.p_num)
                fn.send_msg(send_data, client)

            self.players[addr] = Player()
            self.players[addr].sock = conn
            self.players[addr].addr = addr
            self.players[addr].hall_args['p_num'] = self.p_num
            self.players[addr].hall_args['is_connected'] = True

            self.rlock.release()
            t_recv_msg = MyThread('recv_msg', self.recv_msg, addr)
            t_recv_msg.start()

    def recv_msg(self, addr):
        """接收客户端消息

        Arguments:
            addr -- client address
        Returns:
        """
        while True:
            try:
                if self.players[addr].hall_args['is_connected']:
                    recv_data = self.players[addr].sock.recv(1024)
                else:
                    print("client", addr, "disconnected")
                    self.client_del(addr)
                    return
            except ConnectionResetError:
                print("client", addr, "disconnected")
                self.client_del(addr)
                return

            else:
                recv_data = recv_data.decode('utf-8')
                if recv_data:
                    try:
                        recv_args = eval(recv_data)
                    except:
                        # print("Unexpected error:", sys.exc_info()[0])
                        print(recv_data)
                    else:
                        for key in recv_args.keys():
                            if key in self.players[addr].hall_args.keys():
                                self.players[addr].hall_args[key] = recv_args[key]
                                self.hall_recv_response(addr, key)
                                # print('from client', addr, 'receive', key,
                                #       recv_args[key])
                            elif key in self.players[addr].room_args.keys():
                                self.players[addr].room_args[key] = recv_args[key]

                            elif key in self.players[addr].game_args.keys():
                                self.players[addr].game_args[key] = recv_args[key]

                    print('receive: ', recv_data, 'from', addr)

    def hall_recv_response(self, addr, key):
        """大厅接收信息响应函数

        Arguments:
            key -- key of received message
        """
        if key == 'is_waiting':
            if self.players[addr].hall_args[key]:
                self.players_waiting[addr] = self.players[addr]
            else:
                if self.players_waiting[addr]:
                    del self.players_waiting[addr]

            if len(self.players_waiting) >= 2:
                self.match_found()
        elif key == 'end_flag':
            self.rooms[self.players[addr].room_args['r_index']].end_flag = True

    # def room_recv_update(self, addr):
    #     """房间参数更新函数"""
    #
    #     index = self.players[addr].room_args['r_index']
    #     self.rooms[index].r_args = self.players[addr].room_args

    def match_found(self):
        """匹配成功响应函数

        """
        while len(self.players_waiting) >= 2:
            r_index = len(self.rooms)
            k = 0
            players = []
            keys = []
            for key in self.players_waiting.keys():
                if k >= 2:
                    break
                else:
                    players.append(self.players_waiting[key])
                    keys.append(key)
                    k += 1
            for key in keys:
                self.players[key].hall_args['in_game'] = True
                self.players[key].room_args['r_index'] = r_index
                self.players_waiting.pop(key)

            new_room = Room(players, r_index)
            self.rooms.append(new_room)
            self.r_num = len(self.rooms)
            t_room = MyThread('t_room', new_room.start_room)
            t_room.start()

    def client_del(self, addr):
        """销毁指定客户端相关数据，

        Arguments:
            p_index -- client index number
        Returns:
        """
        self.p_num -= 1

        if self.players[addr].hall_args['in_game']:
            self.rooms[self.players[addr].room_args['r_index']].end_flag = True
            self.r_num -= 1
        else:
            send_data = dict(is_connected=False)
            fn.send_msg(send_data, self.players[addr].sock)

        del self.players[addr]
        del self.clients[addr]


# ###################<<房间类>>################### #
class Room:
    """房间类

    Attributes:
        players -- list of [player_1, player_2]
        r_index -- the index of room
    Methods:
    """
    def __init__(self, players, r_index):
        self.players = players
        self.r_index = r_index

        self.end_flag = False
        self.game_args = {
            'turn_num': 0,  # 回合数
            'my_cards': [],  # 守方手牌列表
            'his_cards': [],  # 攻方手牌列表
            'wait_flag': False,
            'obj': {
                'hp': 100
            },
            'me': {
                'wait_oprt': False,
                'turn_flag': False,
                'win_flag': False,
                'init_card_num': 0,
                'card_num': 0,
                'card_choose': 0,
            },
            'him': {
                'wait_oprt': False,
                'turn_flag': False,
                'win_flag': False,
                'init_card_num': 0,
                'card_num': 0,
                'card_choose': 0,
            },
        }
        self.defender = players[0]
        self.attacker = players[1]
        self.CARD_NUM = 4  # 卡牌种类数
        self.CARDS = CardDict()  # 所有卡牌

    def start_room(self):
        """start room"""
        print('room:', self.r_index, 'started')
        for k in range(len(self.players)):
            send_data = dict(in_game=True, r_index=self.r_index)
            fn.send_msg(send_data, self.players[k].sock)

        # initial game
        self.init_game()

        # wait
        time.sleep(3)

        while True:
            if self.end_flag:
                self.end_room()
                return
            else:
                self.start_turn()

    def init_game(self):
        """initial game"""

        random.seed()
        rand_num = random.randrange(2)
        # 确定阵营
        if rand_num == 0:
            self.defender = self.players[0]
            self.attacker = self.players[1]
        else:
            self.defender = self.players[1]
            self.attacker = self.players[0]
        self.defender.set_game_args(dict(is_def=True))
        self.attacker.set_game_args(dict(is_def=False))
        time.sleep(1)

        # 定义基本属性
        self.game_args['obj']['hp'] = 100
        self.game_args['me']['init_card_num'] = 3
        self.game_args['him']['init_card_num'] = 3

        # 发放初始手牌
        self.get_card('me', self.game_args['me']['init_card_num'])
        self.get_card('him', self.game_args['him']['init_card_num'])

        # turn flag
        # self.game_args['me']['turn_flag'] = True

        # 更新数据
        self.update_data()

    def start_turn(self):
        """start a turn"""

        if self.game_args['turn_num'] == 0:
            self.game_args['me']['turn_flag'] = True

        self.game_args['turn_num'] += 1
        t_flag = True
        if self.game_args['me']['turn_flag']:
            p_flag = 'me'
        else:
            p_flag = 'him'
        self.get_card(p_flag, 2)
        self.update_data()

        while True:
            if self.end_check():
                self.end_flag = True
                t_flag = False
            if not t_flag:
                self.end_turn(p_flag)

                # delay to make sure client close
                time.sleep(2)
                return
            else:
                self.game_args[p_flag]['wait_oprt'] = True
                self.game_args['wait_flag'] = True
                self.update_data()
                oprt = self.wait_oprt(p_flag, 6000)
                self.game_args['wait_flag'] = False

                if oprt == -1:
                    t_flag = False
                else:
                    # 实现卡牌功能
                    self.choose_card(p_flag, oprt)
                    self.update_data()

    def end_turn(self, p_flag):
        """end turn"""
        # turn flag
        if p_flag == 'me':
            self.game_args['me']['turn_flag'] = False
            self.game_args['him']['turn_flag'] = True
        else:
            self.game_args['me']['turn_flag'] = True
            self.game_args['him']['turn_flag'] = False

        self.update_data()

    def wait_oprt(self, p_flag, w_time):
        """waiting for operation

        Arguments:
            p_flag -- waiting for player, 'me' or 'him'
            w_time -- max waiting time
        Returns:
            card_index -- int
        """
        time_0 = time.time()
        while True:
            time_1 = time.time()
            self.recv_update()
            # timeout random operation
            if time_1-time_0 >= w_time:
                print(p_flag, "operation wait timeout", w_time, "s")
                card_index = -1

                # 自动出牌
                # if self.game_args[p_flag]['card_num'] == 0:
                #     self.game_args[p_flag]['wait_oprt'] = False
                #     card_index = -1
                # else:
                #     card_index = random.randrange(self.game_args[p_flag]['card_num'])
                return card_index
            elif not self.game_args[p_flag]['turn_flag']:
                return -1
            elif not self.game_args[p_flag]['wait_oprt']:
                card_index = self.game_args[p_flag]['card_choose']
                return card_index

    def get_card(self, player_key, num):
        """get card

        Arguments:
            player -- who get card
            num -- number of cards
        """

        card_flag = ''
        if player_key == 'me':
            card_flag = 'my_cards'
        elif player_key == 'him':
            card_flag = 'his_cards'
        else:
            print("get card flag error")

        for k in range(num):
            rand_index = random.randrange(self.CARD_NUM)
            self.game_args[card_flag].append(rand_index)
            self.game_args[player_key]['card_num'] += 1

    def choose_card(self, p_flag, index):
        """choose card by index, execute card function

        Arguments:
            p_flag -- 'me' or 'him'
            index -- the index of card in 'my_cards'/'his_cards' list
        """

        # pop cards list and get card key
        card_key = ''
        if p_flag == 'me':
            card_index = self.game_args['my_cards'].pop(index)
            if card_index in [0, 1, 2, 3]:
                card_key = 'regenerate' + str(card_index)
        else:
            card_index = self.game_args['his_cards'].pop(index)
            if card_index in [0, 1, 2, 3]:
                card_key = 'attack' + str(card_index)

        self.game_args[p_flag]['card_num'] -= 1

        # execute card func
        card_func = self.CARDS.cards[card_key].func()
        print('card_func:', card_func)
        for key in card_func.keys():
            if key == 'change_hp':
                self.game_args['obj']['hp'] += card_func[key]

        return

    def update_data(self):
        """update data"""

        args = dict(
            turn_num=self.game_args['turn_num'],
            my_cards=self.game_args['my_cards'],
            his_cards=self.game_args['his_cards'],
            obj=self.game_args['obj'],
            me=self.game_args['me'],
            him=self.game_args['him'],
        )
        self.defender.set_game_args(args)
        self.attacker.set_game_args(args)

        self.defender.send_game_args()
        self.attacker.send_game_args()

    def recv_update(self):
        """update when receive message"""

        if self.game_args['me']['turn_flag']:
            for key in self.defender.game_args.keys():
                self.game_args[key] = self.defender.game_args[key]
        elif self.game_args['him']['turn_flag']:
            for key in self.attacker.game_args.keys():
                self.game_args[key] = self.attacker.game_args[key]
        else:
            print("err, no player in turn", self.game_args['turn_num'])

    def end_check(self):
        """check end condition"""

        if self.game_args['obj']['hp'] >= 200:
            self.game_args['me']['win_flag'] = True
            return True
        elif self.game_args['obj']['hp'] <= 0:
            self.game_args['him']['win_flag'] = True
            return True
        else:
            result = False

        return result

    def end_room(self):
        """end room"""

        for k in range(len(self.players)):
            send_data = dict(in_game=False, r_index=-1, is_connected=False)
            fn.send_msg(send_data, self.players[k].sock)

    def set_args(self):
        """update room args"""
        print('room:', self.r_index, 'updated')


# ###################<<卡牌类>>################### #
class CardDict:
    """卡牌库

    Attributes:
        cards -- dictionary of cards by keys below

    ********** defender cards **********
        regenerate0 -- regenerate card with ad:10
        regenerate1 -- regenerate card with ad:1
        regenerate2 -- regenerate card with ad:2
        regenerate3 -- regenerate card with ad:3

    ********** attacker cards **********
        attack0 -- attack card with ad:10
        attack1 -- attack card with ad:1
        attack2 -- attack card with ad:2
        attack3 -- attack card with ad:3

    Methods:
    """
    cards = {}

    # 定义构造方法
    def __init__(self):
        self.cards['attack0'] = AttackCard(50)
        self.cards['attack1'] = AttackCard(10)
        self.cards['attack2'] = AttackCard(20)
        self.cards['attack3'] = AttackCard(30)

        self.cards['regenerate0'] = RegenerateCard(30)
        self.cards['regenerate1'] = RegenerateCard(20)
        self.cards['regenerate2'] = RegenerateCard(5)
        self.cards['regenerate3'] = RegenerateCard(10)


class Card:
    """卡牌类

    Class attributes:
    ap -- action point (default 0)
    age -- the age (default 0)

    Class methods
    __weight, speak
    """
    ap = 0

    # 定义构造方法
    def __init__(self, a):
        ap = a


class AttackCard:
    """攻击牌

    Attributes:
        ad -- attack damage
    Methods:
        func
    """
    def __init__(self, ad):
        self.ad = ad

    def func(self):
        data = dict(change_hp=-self.ad)
        return data


class RegenerateCard:
    """回复

    Attributes:
        ra -- regenerate amount
    Methods:
        func
    """

    def __init__(self, ra):
        self.ra = ra

    def func(self):
        data = dict(change_hp=self.ra)
        return data
